﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Shared;

namespace Cubicle.Core.Processors
{
    [DataContract]
    [Serializable]
    public class Instruction : DynamicItem
    {

        [XmlAttribute("handle")]
        [DataMember]
        public string Handle;

        [XmlAttribute("memberName")]
        [DataMember]
        public string MemberName;

        [XmlArray("Parameters")]
        [XmlArrayItem("p")]
        [DataMember]
        public InstructionParameter[] Params;

        [XmlAttribute("key")]
        [DataMember]
        public string Key;

        [XmlArray]
        [XmlArrayItem("className")]
        [DataMember]
        public string[] MethodArgs;

        [XmlArray]
        [XmlArrayItem("className")]
        [DataMember]
        public string[] ClassArgs;

        [XmlAttribute("engine")]
        [DataMember]
        public string Engine;

        [XmlAttribute("endTime")]
        [DataMember]
        public DateTime EndTime = DateTime.MaxValue;

        [XmlAttribute("startTime")]
        [DataMember]
        public DateTime StartTime;

        [XmlIgnore]
        public object Result;

        [XmlIgnore]
        private object[] _parameterCache;

        [XmlIgnore]
        [DataMember]
        public object[] Parameters
        {
            get
            {
                if (_parameterCache != null) return _parameterCache;
                if (Params == null) return null;
                _parameterCache = new object[Params.Length];
                for (var i = 0; i < Params.Length; i++)
                    _parameterCache[i] = Params[i].Value;
                return _parameterCache;
            } set
            {
                if (value == null)
                {
                    Params = null;
                    return;
                }
                Params = new InstructionParameter[value.Length];
                for (var i = 0; i < Params.Length; i++)
                    Params[i] = new InstructionParameter(i, 0, value[i]);
                _parameterCache = value;
            }
        }

        public Instruction() {}

        public Instruction(
            string handle,
            string memberName,
            string key,
            object[] parameters,
            string[] methodArgs = null,
            string[] classArgs = null,
            string engine = null)
        {
            Handle = handle;
            MemberName = memberName;
            Parameters = parameters;
            Key = key;
            MethodArgs = methodArgs;
            ClassArgs = classArgs;
            Engine = engine;
        }

        public Instruction(
            string handle,
            string memberName,
            IEnumerable<InstructionParameter> parameters,
            string key,
            string[] methodArgs = null,
            string[] classArgs = null,
            string engine = null)
        {
            Handle = handle;
            MemberName = memberName;
            Params = parameters == null ? null : parameters.ToArray();
            Key = key;
            MethodArgs = methodArgs;
            ClassArgs = classArgs;
            Engine = engine;
        }

        public object[] GetParameters(
            IRepository repo,
            IDictionary<string, string> dict)
        {
            if (Params == null)
                return null;

            // Find parameter size
            var r = 0;
            var c = 0;
            foreach (var t in Params)
            {
                r = Math.Max(r, t.Row);
                c = Math.Max(c, t.Column);
            }

            // Create a jagged array of a 2D array was provided
            var useJagged = r > 0;

            // Check if row attributes should be used or if they should be generated
            var useColAttrib = c >= Params.Length - 1;

            var result = new object[r + 1][];
            var row = 0;
            var col = 0;
            for (int i = 0; i < Params.Length; i++)
            {
                var par = Params[i];
                row = useJagged ? par.Row : 0;
                col = useColAttrib ? par.Column : i;
                if (result[row] == null)
                    result[row] = new object[useColAttrib ? c + 1 : Params.Length];

                if (par.Handle != null)
                    result[row][col] = repo.Get(par.Handle);
                else if (par.Key != null)
                    result[row][col] = dict[par.Key];
                else if (par.Value != null && par.ValueType != null)
                    result[row][col] = Convert.ChangeType(par.Value, Type.GetType(par.ValueType));
                else if (par.Value != null && par.ValueType == null)
                    result[row][col] = par.Value;
                else
                    result[row][col] = null;
            }

            return useJagged ? result : result[0];
        }

    }
}